package observer;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;

/**
 * These lists can be observed by {@link ListObserver}s, i.e. the observers will be notified of any changes.
 *
 * @author Neil Dickson
 */
public final class ObservedList<E extends Copiable> extends ArrayList<E> implements Copiable<ObservedList<E>>, Serializable {
	private ArrayList<ListObserver<E>> observers;

	public ObservedList(int initialCapacity) {
		super(initialCapacity);
		observers = null;
	}

	public ObservedList() {
		super();
		observers = null;
	}

	public ObservedList(Collection<? extends E> c) {
		super(c);
		observers = null;
	}

	public ObservedList(E[] array) {
		this(array==null ? 10 : array.length);
		if (array!=null) {
			for (E e : array) {
				add(e);
			}
		}
	}

	public synchronized void addObserver(ListObserver<E> observer) {
		if (observer==null) {
			throw new NullPointerException();
		}
		if (observers==null) {
			observers = new ArrayList<ListObserver<E>>();
		}
		if (!observers.contains(observer)) {
			observers.add(observer);
		}
	}
	public synchronized void removeObserver(ListObserver<E> observer) {
		if (observers!=null && observers.contains(observer)) {
			observers.remove(observer);
		}
	}

	public synchronized E set(int index, E element) {
		return set(index,element,null);
	}

	public synchronized E set(int index, E element,ListObserver<E> culprit) {
		E ret = super.set(index, element);
		notifyOnSet(culprit,index,ret);
		return ret;
	}

	public synchronized boolean add(E o) {
		return add(o,null);
	}

	public synchronized boolean add(E o,ListObserver<E> culprit) {
		add(size(),o,culprit);
		return true;
	}

	public synchronized void add(int index,E element) {
		add(index,element,null);
	}

	public synchronized void add(int index,E element,ListObserver<E> culprit) {
		super.add(index,element);
		notifyOnAdd(culprit,index);
	}

	public synchronized E remove(int index) {
		return remove(index,null);
	}

	public synchronized E remove(int index,ListObserver<E> culprit) {
		E ret = super.remove(index);
		notifyOnRemove(culprit,index,ret);
		return ret;
	}

	public synchronized E move(int from,int to) {
		return move(from,to,null);
	}

	public synchronized E move(int from,int to,ListObserver<E> culprit) {
		super.add(to,super.remove(from));
		notifyOnMove(culprit,from,to);
		return get(to);
	}

	public synchronized boolean remove(Object o) {
		return remove(o,null);
	}

	public synchronized boolean remove(Object o,ListObserver<E> culprit) {
		int index = indexOf(o);
		if (index>=0) {
			remove(index,culprit);
			return true;
		}
		return false;
	}

	public synchronized void clear() {
		clear(null);
	}

	public synchronized void clear(ListObserver<E> culprit) {
		for (int i=size()-1;i>=0;--i) {
			remove(i,culprit);
		}
	}

	public synchronized boolean addAll(Collection<? extends E> c) {
		return addAll(c,null);
	}

	public synchronized boolean addAll(Collection<? extends E> c,ListObserver<E> culprit) {
		boolean modified = false;
		for (E e : c) {
			modified |= add(e,culprit);
		}
		return modified;
	}

	public synchronized boolean addAll(E[] array) {
		return addAll(array,null);
	}

	public synchronized boolean addAll(E[] array,ListObserver<E> culprit) {
		boolean modified = false;
		for (E e : array) {
			modified |= add(e,culprit);
		}
		return modified;
	}

	public synchronized boolean removeAll(Collection<?> c) {
		return removeAll(c,null);
	}

	public synchronized boolean removeAll(Collection<?> c,ListObserver<E> culprit) {
		boolean modified = false;
		for (Object e : c) {
			modified |= remove(e,culprit);
		}
		return modified;
	}

	public synchronized boolean retainAll(Collection<?> c) {
		return retainAll(c,null);
	}

	public synchronized boolean retainAll(Collection<?> c,ListObserver<E> culprit) {
		boolean modified = false;
		for (int i=size()-1;i>=0;--i) {
			if (!c.contains(get(i))) {
				remove(i,culprit);
				modified = true;
			}
		}
		return modified;
	}

	public synchronized boolean addAll(int index, Collection<? extends E> c) {
		return addAll(index,c,null);
	}

	public synchronized boolean addAll(int index, Collection<? extends E> c,ListObserver<E> culprit) {
		boolean modified = false;
		for (E e : c) {
			add(index,e,culprit);
			++index;
			modified = true;
		}
		return modified;
	}

	protected synchronized void removeRange(int fromIndex, int toIndex) {
		removeRange(fromIndex,toIndex,null);
	}

	protected synchronized void removeRange(int fromIndex, int toIndex,ListObserver<E> culprit) {
		for (int i=toIndex-1;i>=fromIndex;--i) {
			remove(i,culprit);
		}
	}

	private void notifyOnAdd(ListObserver<E> culprit,int index) {
		if (observers!=null) {
			for (ListObserver<E> observer : observers) {
				if (observer!=culprit) {
					observer.listElementAdded(this,index);
				}
			}
		}
	}
	private void notifyOnRemove(ListObserver<E> culprit,int index,E element) {
		if (observers!=null) {
			for (ListObserver<E> observer : observers) {
				if (observer!=culprit) {
					observer.listElementRemoved(this,index,element);
				}
			}
		}
	}
	private void notifyOnMove(ListObserver<E> culprit,int from,int to) {
		if (observers!=null) {
			for (ListObserver<E> observer : observers) {
				if (observer!=culprit) {
					observer.listElementMoved(this,from,to);
				}
			}
		}
	}
	private void notifyOnSet(ListObserver<E> culprit,int index,E oldElement) {
		if (observers!=null) {
			for (ListObserver<E> observer : observers) {
				if (observer!=culprit) {
					observer.listElementSet(this,index,oldElement);
				}
			}
		}
	}

	/**
	 * Deep copies the list of elements (i.e. calls copy() on each element)
	 * @return the deep copy
	 */
	public ObservedList<E> copy() {
		ObservedList<E> that = new ObservedList<E>(size());
		for (E e : this) {
			Object o = e.copy();
			if (e.getClass().isInstance(o)) {
				that.add((E)o);
			}
		}
		return that;
	}

	public E getLast() {
		return get(size()-1);
	}
}
